package it.enricod.jcontextfree.gui.impl;

import static java.lang.Math.min;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;

import javax.swing.JComponent;

import org.apache.log4j.Logger;
import org.jdesktop.swingworker.SwingWorker;

import it.enricod.jcontextfree.engine.model.FigureRegion;
import it.enricod.jcontextfree.engine.model.IPaint;
import it.enricod.jcontextfree.engine.model.IProgram;
import it.enricod.jcontextfree.gui.IProgramPane;
import it.enricod.jcontextfree.gui.paint.IPainter;
import it.enricod.jcontextfree.gui.paint.PainterFactory;

public class ProgramPane implements IProgramPane {

	private JComponent panel;
	private IProgram program;
	private Logger logger = Logger.getLogger("DrawPanel");
	private boolean mustRepaint = false;
	private IPaint translatedPaint = null;
	private IPaint scaledPaint = null;
	/**
	 * bufferedImage in cui e' scritta l'immagine
	 */
	private BufferedImage bufferedImage;

	public ProgramPane() {
		initGui();
	}

	private void initGui() {
		panel = new PaintComponent();
	}

	public JComponent getComponent() {
		return panel;
	}

	public void drawProgram(IProgram program) {
		logger.info("== drawProgram called ");
		this.program = program;

		translatedPaint = program.createPaint().translateInFirstQuadrant();
		recalculateFinalImageData(true);

		new PaintWorker(bufferedImage).execute();
	}

	private void recalculateFinalImageData(boolean clearBufferedImage) {
		FigureRegion translatedArea = translatedPaint.getRegion();
		double finalPaintSize = min(panel.getWidth(), panel.getHeight());

		// FIXME
		if ( finalPaintSize < 1) {
			finalPaintSize = 600;
		}
		
		double scaleFactor = 0d;
		if (translatedArea.getWidth() > translatedArea.getHeight()) {
			scaleFactor = finalPaintSize / translatedArea.getWidth();
		} else {
			scaleFactor = finalPaintSize / translatedArea.getHeight();
		}

		this.scaledPaint = translatedPaint.scale(scaleFactor);
		FigureRegion scaledRegion = scaledPaint.getRegion();
		if (clearBufferedImage) {
			bufferedImage = createEmptyBufferedImage(scaledRegion);
		}
	}

	private class PaintComponent extends JComponent {

		@Override
		public void paintComponent(Graphics g) {
			if (!mustRepaint) {
				return;
			}

			Graphics2D g2 = ((Graphics2D) g.create());

			// colora di bianco
			g2.setColor(Color.WHITE);
			g2.fillRect(0, 0, getWidth(), getHeight());
			recalculateFinalImageData(false);

			logger.info("PaintComponent called. PANEL DIM (" + getWidth() + ", " + getHeight() + "), BUFFER DIM (" + bufferedImage.getWidth() + ", "
					+ bufferedImage.getHeight() + ")");

			g2.drawImage(bufferedImage, 0, 0, this);
			g2.dispose();
		}
	}

	/**
	 * crea la buffered image (grande come richiesto dal disegno) che verra' poi
	 * ridimensionata durante
	 * 
	 * @param figureRegion
	 * @return
	 */
	private BufferedImage createEmptyBufferedImage(FigureRegion figureRegion) {
		// logger.debug("Creo bufferedImage con dimensione (" + area.getWidth()
		// + ", " + area.getHeight() + ")");
		BufferedImage image = new BufferedImage(new Double(figureRegion.getWidth()).intValue(), new Double(figureRegion.getHeight()).intValue(), BufferedImage.TYPE_INT_RGB);

		// metti sfondo bianco
		Graphics2D g2 = image.createGraphics();
		g2.setColor(Color.WHITE);
		g2.fillRect(0, 0, new Double(figureRegion.getWidth()).intValue(), new Double(figureRegion.getHeight()).intValue());
		g2.dispose();

		return image;
	}

	/**
	 * Convenience method that returns a scaled instance of the provided {@code
	 * BufferedImage}.
	 * 
	 * @param img
	 *            the original image to be scaled
	 * @param targetWidth
	 *            the desired width of the scaled instance, in pixels
	 * @param targetHeight
	 *            the desired height of the scaled instance, in pixels
	 * @param hint
	 *            one of the rendering hints that corresponds to {@code
	 *            RenderingHints.KEY_INTERPOLATION} (e.g. {@code
	 *            RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR}, {@code
	 *            RenderingHints.VALUE_INTERPOLATION_BILINEAR}, {@code
	 *            RenderingHints.VALUE_INTERPOLATION_BICUBIC})
	 * @param higherQuality
	 *            if true, this method will use a multi-step scaling technique
	 *            that provides higher quality than the usual one-step technique
	 *            (only useful in downscaling cases, where {@code targetWidth}
	 *            or {@code targetHeight} is smaller than the original
	 *            dimensions, and generally only when the {@code BILINEAR} hint
	 *            is specified)
	 * @return a scaled version of the original {@code BufferedImage}
	 */
	private BufferedImage getScaledInstance(final BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) {
		int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

		BufferedImage ret = img;

		int w, h;
		if (higherQuality) {
			// Use multi-step technique: start with original size, then
			// scale down in multiple passes with drawImage()
			// until the target size is reached
			w = img.getWidth();
			h = img.getHeight();
		} else {
			// Use one-step technique: scale directly from original
			// size to target size with a single drawImage() call
			w = targetWidth;
			h = targetHeight;
		}

		do {
			if (higherQuality && w > targetWidth) {
				w /= 2;
				if (w < targetWidth) {
					w = targetWidth;
				}
			}

			if (higherQuality && h > targetHeight) {
				h /= 2;
				if (h < targetHeight) {
					h = targetHeight;
				}
			}

			BufferedImage tmp = new BufferedImage(w, h, type);
			Graphics2D g2 = tmp.createGraphics();
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
			g2.drawImage(ret, 0, 0, w, h, null);
			g2.dispose();

			ret = tmp;
		} while (w != targetWidth || h != targetHeight);

		return ret;
	}

	private class PaintWorker extends SwingWorker<Integer, Object> {

		private BufferedImage bufferedImage;

		// private PaintOnPanelThread thread;

		public PaintWorker(BufferedImage bufferedImage) {
			this.bufferedImage = bufferedImage;
		}

		@Override
		protected Integer doInBackground() throws Exception {
			IPainter painter = PainterFactory.createJava2DPainter();
			painter.paint(bufferedImage, scaledPaint);
			logger.debug("finished image building .. ");

			return new Integer(0);
		}

		@Override
		protected void done() {
			try {

				Integer result = get();

				mustRepaint = true;
				panel.repaint();

			} catch (InterruptedException ex) {
				java.util.logging.Logger.getLogger(ProgramsTabbedPane.class.getName()).log(Level.SEVERE, null, ex);
			} catch (ExecutionException ex) {
				java.util.logging.Logger.getLogger(ProgramsTabbedPane.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
	}

}
